fix(rest): undo of a historical import preserves the audit timeline (#3549) - #3556
Merged
Conversation
…3549) A `treatAsHistorical` import writes with `preserveAudit` (#3493), keeping the original `updated_at`/`updated_by` and business `readonly` fields instead of stamping-now / stripping them. Its undo route restored the captured pre-import snapshot with a plain write context, so the audit auto-stamp re-wrote `updated_at`/`updated_by` to "now" — silently corrupting the very timeline the historical import had preserved. The undo write context now mirrors the import's own: it carries `preserveAudit` iff the job row is flagged `treat_as_historical`, so restoring `u.before` re-writes the snapshotted audit/timestamp values verbatim. A normal import's undo is unchanged (default stamp/strip). Adds two regression tests in import-job-integration.test.ts asserting the undo write context carries preserveAudit iff the import was historical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 27, 2026 04:41
os-zhuang
added a commit
that referenced
this pull request
Jul 27, 2026
…am (#3493 follow-ups) (#3560) Follow-ups to #3493 / #3549 / #3556 — no behavior change. Real-SQLite end-to-end test (@objectstack/runtime): wires the real ObjectQL engine to the real SqlDriver (better-sqlite3) and proves the preserveAudit seam that mock / in-memory drivers structurally cannot — that context.preserveAudit threads through buildDriverOptions and defeats the SQL driver's updated_at force-stamp on both the forward historical write and the undo restore, and that a business readonly field (closed_at) survives the engine strip all the way to disk. Includes the #3549 undo capstone: restore-under-preserveAudit rolls the timeline back, while a plain restore re-stamps now (the bug #3556 fixed). Docs: the treatAsHistorical schema describe (@objectstack/spec, regenerated references/api/export.mdx) now documents all three effects — FSM skip (#3479), audit-timeline preservation (#3493) and undo mirroring (#3556) — instead of only the state-machine half; protocol/objectql/state-machine.mdx gains a bullet on the symmetric undo behavior. Also confirms (via a codebase audit) that the import-undo route was the only business-record snapshot write-back path, so #3556 needs no sibling fix. Claude-Session: https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景 / 问题
关闭 #3549。
treatAsHistorical(历史数据导入)在 #3493/#3497 中获得了「另一半」能力:导入的写上下文携带preserveAudit,因此会保留原始updated_at/updated_by以及业务readonly字段(closed_at、resolved_by等),而不是「盖上当前时间 / 剥离」。但撤销(undo)路径被遗漏了。撤销一个已完成的导入作业时(
POST /data/import/jobs/:jobId/undo),它会用捕获的导入前快照u.before还原被更新的行——却使用了一个普通写上下文:于是审计自动戳(audit auto-stamp)会把
updated_at/updated_by重写为「现在」,悄悄破坏了历史导入刻意保留的那条审计时间线。换句话说:导入保留了原始时间戳,撤销却把它抹掉了——撤销本应是「还原」,结果反而改写了事实。改动
撤销的写上下文现在镜像导入自身的写上下文:当作业行标记了
treat_as_historical时,携带preserveAudit,从而把快照里的审计/时间戳值(以及任何业务readonly字段)原样写回。row.treat_as_historical已经持久化在导入作业行上(rest-server.ts创建作业时写入treat_as_historical: prepared.treatAsHistorical),因此撤销时无需额外查询即可判定。preserveAudit仅在历史导入时按条件加入。import-runner.ts里导入自身写上下文的写法(treatAsHistorical ? { skipStateMachine: true, preserveAudit: true } : {})。为什么这是「保留」而非「特权」
captureBefore只快照导入负载里出现过的键,所以撤销写回的就是导入前的确切值;带上preserveAudit只是让这些值被原样写回,而不是被审计钩子覆盖。sys_audit_log+ 作业行上的reverted_at),所以「谁在何时撤销」这条事实依然可查——被保留的只是被还原行的业务时间线。测试
packages/rest/src/import-job-integration.test.ts新增两条回归测试(真实 ObjectQL 引擎 + 内存驱动 + RestServer,全链路走真实的 create-job / worker / undo 路由):updateData)的上下文携带preserveAudit: true。preserveAudit(默认 stamp/strip)。为让
treat_as_historical标志在作业行上往返,测试的sys_import_job镜像补充了该字段。(
import-job-integration.test.ts单独运行:13 passed,含 2 条新增。)影响面
@objectstack/restpatch(行为修复)。🤖 Generated with Claude Code
https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE
Generated by Claude Code